﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Management;
using Solution.Test.utils;
using System.Diagnostics;
using System.IO;
using DeviceSync.Controllers;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting.Web;
using System.Web;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net;
using System.Web.Http;
using System.Web.Http.Routing;
using System.Web.Http.Controllers;
using System.Web.Http.Hosting;
using Webservice;

namespace Solution.Test
{
    [TestClass]
    public class UnitTest1
    {
        private static string TEMP_PATH = System.IO.Path.GetTempPath() + @"CloudServiceTest" + Path.DirectorySeparatorChar;
        private static string DEVICE_PATH = TEMP_PATH + @"Device Storage" + Path.DirectorySeparatorChar;

        #region Utilities

        private byte[] ReplaceByte(byte[] src, int pos, byte[] repl)
        {
            byte[] dst = null;

            for (int i = 0; i < src.Length; i++)
            {
                dst = new byte[src.Length];

                Buffer.BlockCopy(src, 0, dst, 0, pos);
                Buffer.BlockCopy(repl, 0, dst, pos, repl.Length);
                Buffer.BlockCopy(src, pos, dst, pos + repl.Length, src.Length - (pos + repl.Length));
            }

            return dst;
        }

        public string Checksum(string filePath)
        {
            using (var md5 = MD5.Create())
            {
                var contents = "";
                using (StreamReader reader = new StreamReader(filePath))
                {
                    contents = reader.ReadToEnd();
                }

                byte[] hash = md5.ComputeHash(System.Text.Encoding.Default.GetBytes(contents));

                return Convert.ToBase64String(hash);
                //return BitConverter.ToString(hash).Replace("-", "");
            }
        }

        #endregion

        #region Private Methods

        private string GenerateVIN()
        {
            string VIN = "";
            int A = char.ConvertToUtf32("A", 0);

            for (int i = 0; i < 17; i++)
            {
                int random = new Random().Next(0, 36);
                string value = "";
                if(random < 26)
                    value = char.ConvertFromUtf32( (A + random) );
                else
                    value = (random - 26).ToString();

                if (value == "I" || value == "O" || value == "Q")
                {
                    i--;
                    continue;
                }
                else
                    VIN += value;

                Thread.Sleep(50);
            }

            return VIN;
        }

        private string LoadDeviceLogFile(string path)
        {
            var contents = "";
            using (StreamReader reader = new StreamReader(path))
            {
                contents = reader.ReadToEnd();
            }

            return contents;
        }

        internal bool corruptLog = false;

        private string CorruptDeviceLog(string contents)
        {
            byte[] bytes = System.Text.Encoding.ASCII.GetBytes(contents);

            int Length = (bytes.Length - 1);
            int random = new Random().Next(Length);

            for (int i = 0; i <= random; i++)
            {
                int pos = new Random().Next(0, Length);
                byte[] inject = new byte[] { (byte)new Random().Next() };

                bytes = this.ReplaceByte(bytes, pos, inject);
            }

            corruptLog = true;

            var returnStr = System.Text.Encoding.Default.GetString(bytes);
            return System.Text.Encoding.Default.GetString(bytes);
        }

        private bool otherFile = false;

        private string RandomFile()
        {
            string[] files = Directory.GetFiles(@"Random");
            int random = new Random().Next(files.Length);

            string filePath = files[random];

            Debug.Print("Injecting random file of type {0}", Path.GetExtension(filePath));

            otherFile = true;

            return filePath;
        }

        private string ModifyDeviceLog()
        {
            return "";
        }

        private void InterruptNetworkConnection()
        {
            try
            {
                SystemController localMachine = new SystemController();
                localMachine.DisableAllNetworkAdapters();
            }
            catch (System.Exception ex)
            {
                Debug.Print(ex.Message);
            }
        }

        #endregion

        [TestMethod]
        public void TrainingSamples()
        {
            string[] subdirectories = Directory.GetDirectories(@"Training Samples");

            foreach (string directory in subdirectories)
            {
                this.LoadDeviceLogFiles(true, directory);

                string[] deviceLogs = Directory.GetFiles(DEVICE_PATH);
                Array.Sort(deviceLogs, new NaturalStringComparer());

                string VIN = this.GenerateVIN();

                int random = new Random().Next(1000);

                foreach (string logPath in deviceLogs)
                {
                    if (random == 0 && !otherFile)
                        this.UploadDeviceLogToGateway(VIN, this.RandomFile(), false);

                    this.UploadDeviceLogToGateway(VIN, logPath);
                }
            }
        }

        [TestMethod]
        public void RunAllLogs()
        {
            string[] subdirectories = Directory.GetDirectories(@"Datasets");

            foreach (string directory in subdirectories)
            {
                this.LoadDeviceLogFiles(true, directory);

                string[] deviceLogs = Directory.GetFiles(DEVICE_PATH);
                Array.Sort(deviceLogs, new NaturalStringComparer());

                string VIN = this.GenerateVIN();

                int random = new Random().Next(1000);

                foreach (string logPath in deviceLogs)
                {
                    if (random == 0 && !otherFile)
                        this.UploadDeviceLogToGateway(VIN, this.RandomFile(), false);

                    this.UploadDeviceLogToGateway(VIN, logPath);
                }
            }
        }

        [TestMethod]
        public void EndToEnd()
        {
            switch (new Random().Next(0,0))
            {
                case 0:
                    this.LoadDeviceLogFiles(true);
                    break;
                case 1:
                    this.GenerateDeviceLogFiles();
                    break;
            }
            
            string[] deviceLogs = Directory.GetFiles(DEVICE_PATH);
            Array.Sort(deviceLogs, new NaturalStringComparer());

            string VIN = this.GenerateVIN();

            int random = new Random().Next(1000);

            foreach (string logPath in deviceLogs)
            {
                //this.PutDeviceLogToGateway("VIN", logPath);

                if (random == 0 && !otherFile)
                    this.UploadDeviceLogToGateway(VIN, this.RandomFile(), false);

                this.UploadDeviceLogToGateway(VIN, logPath);
            }

            //this.UploadDeviceLogToGateway(VIN, @"Resources\DoneFile.txt");

            //this.StreamDeviceLogsToGateway(VIN, deviceLogs);
        }

        private void LoadDeviceLogFiles(bool real = false, string directory = null)
        {
            if (directory == null)
            {
                string[] subdirectories = Directory.GetDirectories(@"Datasets");
                int index = new Random().Next(0, (subdirectories.Length - 1));
                directory = subdirectories[index];
            }
            

            Debug.Print("Loading device logs from {0}...", directory);

            string[] files = Directory.GetFiles(directory);

            if (Directory.Exists(DEVICE_PATH))
            {
                Directory.Delete(DEVICE_PATH, true);
                Thread.Sleep(500);
            }

            Directory.CreateDirectory(DEVICE_PATH);

            Thread.Sleep(500);

            Debug.Print("Copying files to {0}...", DEVICE_PATH);

            File.WriteAllText(DEVICE_PATH + "log_0.ilf", "[");

            foreach (string filePath in files)
            {
                string filename = Path.GetFileName(filePath);
                string newPath = DEVICE_PATH + filename;

                File.Copy(filePath, newPath);
            }

            File.WriteAllText(DEVICE_PATH + "log_z.ilf", "]");
        }

        private void GenerateDeviceLogFiles()
        {
            Debug.Print("Generating device logs...");

            DataGenerator generator = new DataGenerator(false, false, 2400);

            if (Directory.Exists(DEVICE_PATH))
            {
                Directory.Delete(DEVICE_PATH, true);
                Thread.Sleep(500);
            }

            Directory.CreateDirectory(DEVICE_PATH);

            Thread.Sleep(500);

            Debug.Print("Copying files to {0}...", DEVICE_PATH);

            File.WriteAllText(DEVICE_PATH + "log_0.ilf", "[");

            int index = 1;
            foreach (string contents in generator.GenerateLogFiles())
            {
                string filename = "log_" + index + ".ilf";
                string newPath = DEVICE_PATH + filename;

                File.WriteAllText(Path.Combine(DEVICE_PATH,filename), contents);
                index++;
            }

            File.WriteAllText(DEVICE_PATH + "log_z.ilf", "]");
        }

        #region Local Storage

        [UrlToTest("http://localhost:63191/")]
        [HostType("ASP.NET")]
        [AspNetDevelopmentServerHost("$(SolutionDir)\\DeviceSync")]
        private void StreamDeviceLogsToGateway(string id, string[] logs)
        {
            var request = (HttpWebRequest)WebRequest.Create("http://localhost:63191/api/Device/Put?id=" + id + "&checksum=");

            request.Method = "POST";

            int contentLength = 0;
            foreach (string log in logs)
            {
                byte[] buffer = File.ReadAllBytes(log);
                contentLength = (contentLength + buffer.Length);
            }

            request.ContentLength = contentLength;
            request.ContentType = "application/json";
            request.Timeout = 5000 * 60;

            using (Stream stream = request.GetRequestStream())
            {
                foreach (string log in logs)
                {
                    byte[] buffer = File.ReadAllBytes(log);
                    stream.Write(buffer, 0, buffer.Length);
                }
            }

            var response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode == HttpStatusCode.OK)
                Debug.Print("Stream OK");
            else
                Debug.Print("Stream returned {0}", response.StatusCode);
        }

        /// <summary>
        /// Upload log file to Gateway
        /// </summary>
        /// <param name="id"></param>
        /// <param name="filePath"></param>
        [UrlToTest("http://localhost:63191/")]
        [HostType("ASP.NET")]
        [AspNetDevelopmentServerHost("$(SolutionDir)\\DeviceSync")]
        private void UploadDeviceLogToGateway(string id, string filePath, bool proper=true)
        {
            string ContentDisposition = "form-data; name=\"{0}\"; filename=\"{1}\"";

            Task<HttpResponseMessage> actual;

            string contents = "";
            using (StreamReader reader = new StreamReader(filePath))
            {
                contents = reader.ReadToEnd();
            }

            var config = new HttpConfiguration();

            var checksum = HttpUtility.UrlEncodeUnicode( this.Checksum(filePath) );
            var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:63191/api/Device/Put?id=" + id + "&checksum=" + checksum);

            var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
            var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "Device" } });
            
            DeviceController controller = new DeviceController();
            controller.CompletedMerge += controller_CompletedMerge;
            controller.ControllerContext = new HttpControllerContext(config, routeData, request);
            controller.Request = request;
            controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;

            int randomAction = new Random().Next(100);

            bool corrupted = false;
            StringContent fileContent;
            if (randomAction == 17 && !corruptLog)
            {
                Debug.Print("ACTION: Corrupting {0}...", filePath);
                fileContent = new StringContent(this.CorruptDeviceLog(contents));
                corrupted = true;
            }
            else
            {
                fileContent = new StringContent(contents);
            }

            fileContent.Headers.Add("Content-Disposition", String.Format(ContentDisposition, "", Path.GetFileName(filePath)));

            MultipartFormDataContent multipart = new MultipartFormDataContent();
            multipart.Add(fileContent);

            controller.Request.Content = multipart;

            actual = controller.Put(id, checksum);
            Thread.Sleep(10);

            if(corrupted)
            {
                HttpResponseMessage expected = new HttpResponseMessage(HttpStatusCode.BadRequest);
                if (expected.StatusCode != actual.Result.StatusCode)
                    Debug.Print("ERROR: Expecting a status code of 400(Bad Request) but instead received {0}", actual.Result.StatusCode);

                Debug.Print("Server returned a 400 Bad Request response, resending file...");
                this.UploadDeviceLogToGateway(id, filePath);
                //Assert.AreEqual(expected.StatusCode, actual.Result.StatusCode, "Expected 400(Bad Request)");
            }
            else if(!proper)
            {
                HttpResponseMessage expected = new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType);
                if (expected.StatusCode != actual.Result.StatusCode)
                    Debug.Print("ERROR: Expecting a status code of 415(UnsuportedMediaType) but instead received {0}", actual.Result.StatusCode);

                Debug.Print("Server returned a 415 Bad Request response, skipping file");
                //this.UploadDeviceLogToGateway(id, filePath);
            }
            else
            {
                HttpResponseMessage expected = new HttpResponseMessage(HttpStatusCode.OK);
                if (expected.StatusCode != actual.Result.StatusCode)
                    Debug.Print("ERROR: Expecting a status code of 200(OK) but instead received {0}", actual.Result.StatusCode);
                //Assert.AreEqual(expected.StatusCode, actual.Result.StatusCode, "Expected 200(OK)");
            }
        }

        void controller_CompletedMerge(string filePath)
        {
            Debug.Print("Device files have been uploaded and the file merge has been completed.");

            this.CompareGatewayLogToDeviceLogs(filePath);
        }

        /// <summary>
        /// Puts log file on Gateway
        /// </summary>
        /// <param name="id"></param>
        /// <param name="filePath"></param>
        private void PutDeviceLogToGateway(string id, string filePath)
        {
            string contents = "";
            using (StreamReader reader = new StreamReader(filePath))
            {
                contents = reader.ReadToEnd();
            }

            int randomAction = new Random().Next();

            DeviceController gateway = new DeviceController();
            if ((randomAction % 30) == 0 && !corruptLog)
            {
                Debug.Print("Corrupting {0}...", filePath);
                try
                {
                    gateway.Put(id, this.Checksum(filePath), this.CorruptDeviceLog(contents));
                    Assert.Fail("No expection was thrown");
                }
                catch (System.ArgumentException ex)
                {
                    Debug.Print("An System.ArgumentException was thrown. {0}", ex.Message);
                    Assert.IsInstanceOfType(ex, typeof(System.ArgumentException), "Expected an ArgumentException");
                }
            }
            else
            {
                try
                {
                    gateway.Put(id, this.Checksum(filePath), contents);
                }
                catch (SystemException ex)
                {
                    Assert.Fail("An unexpected exception was thrown. {0}", ex.Message); 
                }
            } 
        }

        private void CompareGatewayLogToDeviceLogs(string filePath)
        {
            Debug.Print("Comparing logs files on the device with processed log file on gateway...");

            string[] deviceFiles = Directory.GetFiles(DEVICE_PATH);
            Array.Sort(deviceFiles, new NaturalStringComparer());

            StringBuilder sb = new StringBuilder();
            foreach (string devicePath in deviceFiles)
            {
                using (StreamReader reader = new StreamReader(devicePath))
                {
                    sb.Append(reader.ReadToEnd());
                }
            }
            sb.Replace(",]", "]");

            string gatewayLog = "";
            try
            {
                using (StreamReader reader = new StreamReader(filePath))
                {
                    gatewayLog = reader.ReadToEnd();
                }
            }
            catch (SystemException ex)
            {
                Debug.Print("ERROR: An exception was thrown. {0}", ex.Message);
            }
            

            if (sb.ToString() == gatewayLog)
                Debug.Print("The device logs on the device are consistent with the gateway log file.");
            else
                Debug.Print("ERROR: Log files do not match");

            this.UploadDataLogToCloud(gatewayLog);
        }

        #endregion

        [UrlToTest("http://localhost:57820/")]
        [HostType("ASP.NET")]
        [AspNetDevelopmentServerHost("$(SolutionDir)\\DeviceSync")]
        private void UploadDataLogToCloud(string data)
        {
            Debug.Print("Uploading datafile to cloud service...");

            StorageService serviceClient = new StorageService();
            try
            {
                serviceClient.UploadLog(data);
            }
            catch (System.FormatException ex)
            {
                Debug.Print("Data was not properly formatted and therefore rejected. {0}", ex.Message);
            }
            catch (SystemException ex)
            {
                Debug.Print("An unknown exception was caught. {0}", ex.Message);
            }

            Debug.Print("File uploaded");
        }

        // Corrupt

        // Modify

        // Interrupt Connection

    }
}
